stuff in our mainline code. Using hooks, the function becomes:
function showAnArticle( $article ) {
-
if ( Hooks::run( 'ArticleShow', array( &$article ) ) ) {
-
# code to actually show the article goes here
Hooks::run( 'ArticleShowComplete', array( &$article ) );
'ArticlePageDataBefore': Before loading data of an article from the database.
&$wikiPage: WikiPage (object) that data will be loaded
&$fields: fields (array) to load from the database
+&$tables: tables (array) to load from the database
+&$joinConds: join conditions (array) to load from the database
'ArticlePrepareTextForEdit': Called when preparing text to be saved.
$wikiPage: the WikiPage being saved
$create: Whether or not the restoration caused the page to be created (i.e. it
didn't exist before).
$comment: The comment associated with the undeletion.
-$oldPageId: ID of page previously deleted (from archive table)
+$oldPageId: ID of page previously deleted (from archive table). This ID will be used
+ for the restored page.
+$restoredPages: Set of page IDs that have revisions restored for this undelete,
+ with keys being page IDs and values are 'true'.
'ArticleUndeleteLogEntry': When a log entry is generated but not yet saved.
$pageArchive: the PageArchive object
in a Category page. Gives extensions the opportunity to batch load any
related data about the pages.
$type: The category type. Either 'page', 'file' or 'subcat'
-$res: Query result from DatabaseBase::select()
+$res: Query result from Wikimedia\Rdbms\IDatabase::select()
'CategoryViewer::generateLink': Before generating an output link allow
extensions opportunity to generate a more specific or relevant link.
$unpatrolled: Whether or not we are showing unpatrolled changes.
$watched: Whether or not the change is watched by the user.
-'ChangesListSpecialPageFilters': Called after building form options on pages
+'ChangesListSpecialPageFilters': DEPRECATED! Use 'ChangesListSpecialPageStructuredFilters'
+instead.
+Called after building form options on pages
inheriting from ChangesListSpecialPage (in core: RecentChanges,
RecentChangesLinked and Watchlist).
$special: ChangesListSpecialPage instance
'ChangesListSpecialPageQuery': Called when building SQL query on pages
inheriting from ChangesListSpecialPage (in core: RecentChanges,
RecentChangesLinked and Watchlist).
+Do not use this to implement individual filters if they are compatible with the
+ChangesListFilter and ChangesListFilterGroup structure.
+Instead, use sub-classes of those classes, in conjunction with the
+ChangesListSpecialPageStructuredFilters hook.
+This hook can be used to implement filters that do not implement that structure,
+or custom behavior that is not an individual filter.
$name: name of the special page, e.g. 'Watchlist'
&$tables: array of tables to be queried
&$fields: array of columns to select
&$join_conds: join conditions for the tables
$opts: FormOptions for this request
+'ChangesListSpecialPageStructuredFilters': Called to allow extensions to register
+filters for pages inheriting from ChangesListSpecialPage (in core: RecentChanges,
+RecentChangesLinked, and Watchlist). Generally, you will want to construct
+new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects.
+When constructing them, you specify which group they belong to. You can reuse
+existing groups (accessed through $special->getFilterGroup), or create your own
+(ChangesListBooleanFilterGroup or ChangesListStringOptionsFilterGroup).
+If you create new groups, you must register them with $special->registerFilterGroup.
+Note that this is called regardless of whether the user is currently using
+the new (structured) or old (unstructured) filter UI. If you want your boolean
+filter to show on both the new and old UI, specify all the supported fields.
+These include showHide, label, and description.
+See the constructor of each ChangesList* class for documentation of supported
+fields.
+$special: ChangesListSpecialPage instance
+
'ChangeTagAfterDelete': Called after a change tag has been deleted (that is,
removed from all revisions and log entries to which it was applied). This gives
extensions a chance to take it off their books.
$rc: RecentChange being tagged when the tagging accompanies the action or null
$user: User who performed the tagging when the tagging is subsequent to the action or null
+'ChangeTagsAllowedAdd': Called when checking if a user can add tags to a change.
+&$allowedTags: List of all the tags the user is allowed to add. Any tags the
+ user wants to add ($addTags) that are not in this array will cause it to fail.
+ You may add or remove tags to this array as required.
+$addTags: List of tags user intends to add.
+$user: User who is adding the tags.
+
'ChangeUserGroups': Called before user groups are changed.
$performer: The User who will perform the change
$user: The User whose groups will be changed
a given content model name, but no entry for that model exists in
$wgContentHandlers.
Note: if your extension implements additional models via this hook, please
-use GetContentModels hook to make them known to core.
+use GetContentModels hook to make them known to core.
$modeName: the requested content model name
&$handler: set this to a ContentHandler object, if desired.
&$ret: the HTML line
$row: the DB row for this line
&$classes: the classes to add to the surrounding <li>
+&$attribs: associative array of other HTML attributes for the <li> element.
+ Currently only data attributes reserved to MediaWiki are allowed
+ (see Sanitizer::isReservedDataAttribute).
'ContributionsToolLinks': Change tool links above Special:Contributions
$id: User identifier
&$ret: the HTML line
$row: the DB row for this line
&$classes: the classes to add to the surrounding <li>
+&$attribs: associative array of other HTML attributes for the <li> element.
+ Currently only data attributes reserved to MediaWiki are allowed
+ (see Sanitizer::isReservedDataAttribute).
'DifferenceEngineAfterLoadNewText': called in DifferenceEngine::loadNewText()
after the new revision's content has been loaded into the class member variable
&$buttons: Array of edit buttons "Save", "Preview", "Live", and "Diff"
&$tabindex: HTML tabindex of the last edit check/button
-'EditPageBeforeEditChecks': Allows modifying the edit checks below the textarea
-in the edit form.
+'EditPageBeforeEditChecks': DEPRECATED! Use 'EditPageGetCheckboxesDefinition' instead,
+or 'EditPage::showStandardInputs:options' if you don't actually care about checkboxes
+and just want to add some HTML to the page.
+Allows modifying the edit checks below the textarea in the edit form.
&$editpage: The current EditPage object
-&$checks: Array of edit checks like "watch this page"/"minor edit"
+&$checks: Array of the HTML for edit checks like "watch this page"/"minor edit"
&$tabindex: HTML tabindex of the last edit check/button
'EditPageBeforeEditToolbar': Allows modifying the edit toolbar above the
textarea in the edit form.
-&$toolbar: The toolbar HTMl
+&$toolbar: The toolbar HTML
+Hook subscribers can return false to avoid the default toolbar code being loaded.
'EditPageCopyrightWarning': Allow for site and per-namespace customization of
contribution/copyright notice.
&$msg: localization message name, overridable. Default is either
'copyrightwarning' or 'copyrightwarning2'.
+'EditPageGetCheckboxesDefinition': Allows modifying the edit checkboxes
+below the textarea in the edit form.
+$editpage: The current EditPage object
+&$checkboxes: Array of checkbox definitions. See EditPage::getCheckboxesDefinition()
+for the format.
+
'EditPageGetDiffContent': Allow modifying the wikitext that will be used in
"Show changes". Note that it is preferable to implement diff handling for
different data types using the ContentHandler facility.
$block: An array of RecentChange objects in that block
$rc: The RecentChange object for this line
&$classes: An array of classes to change
+&$attribs: associative array of other HTML attributes for the <tr> element.
+ Currently only data attributes reserved to MediaWiki are allowed
+ (see Sanitizer::isReservedDataAttribute).
'EnhancedChangesListModifyBlockLineData': to alter data used to build
a non-grouped recent change line in EnhancedChangesList.
change the tables headers.
&$extTypes: associative array of extensions types
-'ExtractThumbParameters': DEPRECATED! Media handler should override
-MediaHandler::parseParamString instead.
-Called when extracting thumbnail parameters from a thumbnail file name.
-$thumbname: the base name of the thumbnail file
-&$params: the currently extracted params (has source name, temp or archived
-zone)
-
'FetchChangesList': When fetching the ChangesList derivative for a particular
user.
$user: User the list is being fetched for
'GetIP': modify the ip of the current user (called only once).
&$ip: string holding the ip as determined so far
+'GetLangPreferredVariant': Called in LanguageConverter#getPreferredVariant() to
+ allow fetching the language variant code from cookies or other such
+ alternative storage.
+&$req: language variant from the URL (string) or boolean false if no variant
+ was specified in the URL; the value of this variable comes from
+ LanguageConverter#getURLVariant()
+
'GetLinkColours': modify the CSS class of an array of page links.
$linkcolour_ids: array of prefixed DB keys of the pages linked to,
indexed by page_id.
Return false to stop further processing of the tag
$reader: XMLReader object
+'ImportHandleUnknownUser': When a user doesn't exist locally, this hook is called
+to give extensions an opportunity to auto-create it. If the auto-creation is
+successful, return false.
+$name: User name
+
'ImportHandleUploadXMLTag': When parsing a XML tag in a file upload.
Return false to stop further processing of the tag
$reader: XMLReader object
&$attribs: the attributes to be applied
&$ret: the value to return if your hook returns false
+'LogEventsListLineEnding': Called before a Special:Log line is finished
+$page: the LogEventsList object
+&$ret: the HTML line
+$entry: the DatabaseLogEntry object for this row
+&$classes: the classes to add to the surrounding <li>
+&$attribs: associative array of other HTML attributes for the <li> element.
+ Currently only data attributes reserved to MediaWiki are allowed
+ (see Sanitizer::isReservedDataAttribute).
+
+
'HtmlPageLinkRendererBegin':
Used when generating internal and interwiki links in
LinkRenderer, before processing starts. Return false to skip default
$old: the ?old= param value from the url
$new: the ?new= param value from the url
+'NewPagesLineEnding': Called before a NewPages line is finished.
+$page: the SpecialNewPages object
+&$ret: the HTML line
+$row: the database row for this page (the recentchanges record and a few extras - see
+ NewPagesPager::getQueryInfo)
+&$classes: the classes to add to the surrounding <li>
+&$attribs: associative array of other HTML attributes for the <li> element.
+ Currently only data attributes reserved to MediaWiki are allowed
+ (see Sanitizer::isReservedDataAttribute).
+
'NewRevisionFromEditComplete': Called when a revision was inserted due to an
edit.
$wikiPage: the WikiPage edited
$rev: the new revision
$baseID: the revision ID this was based off, if any
$user: the editing user
+ &$tags: tags to apply to the edit and recent change
'OldChangesListRecentChangesLine': Customize entire recent changes line, or
return false to omit the line from RecentChanges and Watchlist special pages.
&$changeslist: The OldChangesList instance.
&$s: HTML of the form "<li>...</li>" containing one RC entry.
$rc: The RecentChange object.
-&$classes: array of css classes for the <li> element
+&$classes: array of css classes for the <li> element.
+&$attribs: associative array of other HTML attributes for the <li> element.
+ Currently only data attributes reserved to MediaWiki are allowed
+ (see Sanitizer::isReservedDataAttribute).
'OpenSearchUrls': Called when constructing the OpenSearch description XML. Hooks
can alter or append to the array of URLs for search & suggestion formats.
$title: the Title of the rendered page.
$parserOutput: ParserOutput resulting from rendering the page.
+'OtherAutoblockLogLink': Get links to the autoblock log from extensions which
+autoblocks users and/or IP addresses too.
+&$otherBlockLink: An array with links to other autoblock logs
+
'OtherBlockLogLink': Get links to the block log from extensions which blocks
users and/or IP addresses too.
&$otherBlockLink: An array with links to other block logs
&$row: the revision row for this line
&$s: the string representing this parsed line
&$classes: array containing the <li> element classes
+&$attribs: associative array of other HTML attributes for the <li> element.
+ Currently only data attributes reserved to MediaWiki are allowed
+ (see Sanitizer::isReservedDataAttribute).
'PageHistoryPager::doBatchLookups': Called after the pager query was run, before
any output is generated, to allow batch lookups for prefetching information
&$pager: the pager
&$queryInfo: the query parameters
-'PageRenderingHash': Alter the parser cache option hash key. A parser extension
+'PageRenderingHash': NOTE: Consider using ParserOptionsRegister instead.
+Alter the parser cache option hash key. A parser extension
which depends on user options should install this hook and append its values to
the key.
&$confstr: reference to a hash key string which can be modified
&$params: 2-D array of parameters
$parser: Parser object that called the hook
+'ParserOptionsRegister': Register additional parser options. Note that if you
+change the default value for an option, all existing parser cache entries will
+be invalid. To avoid bugs, you'll need to handle that somehow (e.g. with the
+RejectParserCacheValue hook) because MediaWiki won't do it for you.
+&$defaults: Set the default value for your option here.
+&$inCacheKey: To fragment the parser cache on your option, set a truthy value here.
+&$lazyLoad: To lazy-initialize your option, set it null in $defaults and set a
+ callable here. The callable is passed the ParserOptions object and the option
+ name.
+
+'ParserOutputPostCacheTransform': Called from ParserOutput::getText() to do
+post-cache transforms.
+$parserOutput: The ParserOutput object.
+&$text: The text being transformed, before core transformations are done.
+&$options: The options array being used for the transformation.
+
'ParserSectionCreate': Called each time the parser creates a document section
from wikitext. Use this to apply per-section modifications to HTML (like
wrapping the section in a DIV). Caveat: DIVs are valid wikitext, and a DIV
$form: PreferencesForm object, also a ContextSource
$user: User object with preferences to be saved set
&$result: boolean indicating success
+$oldUserOptions: array with user old options (before save)
'PreferencesGetLegend': Override the text used for the <legend> of a
preferences section.
'RecentChange_save': Called at the end of RecentChange::save().
&$recentChange: RecentChange object
+'RecentChangesPurgeRows': Called when old recentchanges rows are purged, after
+deleting those rows but within the same transaction.
+$rows: The deleted rows as an array of recentchanges row objects (with up to
+ $wgUpdateRowsPerQuery items).
+
'RedirectSpecialArticleRedirectParams': Lets you alter the set of parameter
names such as "oldid" that are preserved when using redirecting special pages
such as Special:MyPage and Special:MyTalk.
or request state must be added through MakeGlobalVariablesScript instead.
&$vars: array( variable name => value )
-'ResourceLoaderGetLessVars': Called in ResourceLoader::getLessVars after
-variables from $wgResourceLoaderLESSVars are added. Can be used to add
-context-based variables.
+'ResourceLoaderGetLessVars': DEPRECATED! Called in ResourceLoader::getLessVars
+to add global LESS variables. Loaded after $wgResourceLoaderLESSVars is added.
+Global LESS variables are deprecated. Use ResourceLoaderModule::getLessVars()
+instead to expose variables only in modules that need them.
&$lessVars: array of variables already added
+'ResourceLoaderJqueryMsgModuleMagicWords': Called in
+ResourceLoaderJqueryMsgModule to allow adding magic words for jQueryMsg.
+The value should be a string, and they can depend only on the
+ResourceLoaderContext.
+$context: ResourceLoaderContext
+&$magicWords: Associative array mapping all-caps magic word to a string value
+
'ResourceLoaderRegisterModules': Right before modules information is required,
such as when responding to a resource
loader request or generating HTML output.
added to any module.
&$ResourceLoader: object
-'RevisionInsertComplete': Called after a revision is inserted into the database.
-&$revision: the Revision
-$data: the data stored in old_text. The meaning depends on $flags: if external
- is set, it's the URL of the revision text in external storage; otherwise,
- it's the revision text itself. In either case, if gzip is set, the revision
- text is gzipped.
-$flags: a comma-delimited list of strings representing the options used. May
- include: utf8 (this will always be set for new revisions); gzip; external.
+'RevisionRecordInserted': Called after a revision is inserted into the database.
+$revisionRecord: the RevisionRecord that has just been inserted.
+
+'RevisionInsertComplete': DEPRECATED! Use RevisionRecordInserted hook instead.
+Called after a revision is inserted into the database.
+$revision: the Revision
+$data: DEPRECATED! Always null!
+$flags: DEPRECATED! Always null!
'SearchableNamespaces': An option to modify which namespaces are searchable.
&$arr: Array of namespaces ($nsId => $name) which will be used.
$terms: String of the search terms entered
$specialSearch: The SpecialSearch object
&$query: Array of query string parameters for the link representing the search result.
+&$attributes: Array of title link attributes, can be modified by extension.
'SidebarBeforeOutput': Allows to edit sidebar just before it is output by skins.
Warning: This hook is run on each display. You should consider to use
&$item: HTML to be returned. Will be wrapped in <li></li> after the hook finishes
$row: Database row object
-'SpecialListusersHeader': Called before closing the <fieldset> in
+'SpecialListusersHeader': Called after adding the submit button in
UsersPager::getPageHeader().
$pager: The UsersPager instance
&$out: The header HTML
&$title: If the hook returns false, a Title object to use instead of the
result from the normal query
-'SpecialRecentChangesFilters': DEPRECATED! Use ChangesListSpecialPageFilters
+'SpecialRecentChangesFilters': DEPRECATED! Use ChangesListSpecialPageStructuredFilters
instead.
Called after building form options at RecentChanges.
$special: the special page object
&$extraOpts: array of added items, to which can be added
$opts: FormOptions for this request
-'SpecialRecentChangesQuery': DEPRECATED! Use ChangesListSpecialPageQuery
-instead.
+'SpecialRecentChangesQuery': DEPRECATED! Use ChangesListSpecialPageStructuredFilters
+or ChangesListSpecialPageQuery instead.
Called when building SQL query for SpecialRecentChanges and
SpecialRecentChangesLinked.
&$conds: array of WHERE conditionals for query
$title: The title the 'go' feature has decided to forward the user to
&$url: Initially null, hook subscribers can set this to specify the final url to redirect to
-'SpecialSearchNogomatch': Called when user clicked the "Go" button but the
-target doesn't exist.
+'SpecialSearchNogomatch': Called when the 'Go' feature is triggered (generally
+from autocomplete search other than the main bar on Special:Search) and the
+target doesn't exist. Full text search results are generated after this hook is
+called.
&$title: title object generated from the text entered by the user
'SpecialSearchPowerBox': The equivalent of SpecialSearchProfileForm for
$wgVersion: Current $wgVersion for you to use
&$versionUrl: Raw url to link to (eg: release notes)
-'SpecialWatchlistFilters': DEPRECATED! Use ChangesListSpecialPageFilters
+'SpecialWatchlistFilters': DEPRECATED! Use ChangesListSpecialPageStructuredFilters
instead.
Called after building form options at Watchlist.
$special: the special page object
inserted to rc_type so they can be returned as part of the watchlist.
&$nonRevisionTypes: array of values in the rc_type field of recentchanges table
-'SpecialWatchlistQuery': DEPRECATED! Use ChangesListSpecialPageQuery instead.
+'SpecialWatchlistQuery': DEPRECATED! Use ChangesListSpecialPageStructuredFilters
+or ChangesListSpecialPageQuery instead.
Called when building sql query for SpecialWatchlist.
&$conds: array of WHERE conditionals for query
&$tables: array of tables to be queried
$title: title object related to the revision
$rev: revision (object) that will be viewed
+'UnitTestsAfterDatabaseSetup': Called right after MediaWiki's test infrastructure
+has finished creating/duplicating core tables for unit tests.
+$database: Database in question
+$prefix: Table prefix to be used in unit tests
+
+'UnitTestsBeforeDatabaseTeardown': Called right before MediaWiki tears down its
+database infrastructure used for unit tests.
+
'UnitTestsList': Called when building a list of paths containing PHPUnit tests.
Since 1.24: Paths pointing to a directory will be recursively scanned for
test case files matching the suffix "Test.php".
instead.
&$form: UploadForm object
+'UploadForm:getInitialPageText': After the initial page text for file uploads
+is generated, to allow it to be altered.
+&$pageText: the page text
+$msg: array of header messages
+$config: Config object
+
'UploadForm:initial': Before the upload form is generated. You might set the
member-variables $uploadFormTextTop and $uploadFormTextAfterSummary to inject
text (HTML) either before or after the editform.
$performer: User who performed the change, false if via autopromotion
$reason: The reason, if any, given by the user performing the change,
false if via autopromotion.
+$oldUGMs: An associative array (group name => UserGroupMembership object) of
+the user's group memberships before the change.
+$newUGMs: An associative array (group name => UserGroupMembership object) of
+the user's current group memberships.
'UserIsBlockedFrom': Check if a user is blocked from a specific page (for
specific block exemptions).
$add: Array of strings corresponding to groups added
$remove: Array of strings corresponding to groups removed
-'UserSaveOptions': Called just before saving user preferences/options.
-$user: User object
-&$options: Options, modifiable
+'UserSaveOptions': Called just before saving user preferences. Hook handlers can either add or
+manipulate options, or reset one back to it's default to block changing it. Hook handlers are also
+allowed to abort the process by returning false, e.g. to save to a global profile instead. Compare
+to the UserSaveSettings hook, which is called after the preferences have been saved.
+$user: The User for which the options are going to be saved
+&$options: The users options as an associative array, modifiable
-'UserSaveSettings': Called when saving user settings.
-$user: User object
+'UserSaveSettings': Called directly after user preferences (user_properties in the database) have
+been saved. Compare to the UserSaveOptions hook, which is called before.
+$user: The User for which the options have been saved
'UserSetCookies': DEPRECATED! If you're trying to replace core session cookie
handling, you want to create a subclass of MediaWiki\Session\CookieSessionProvider
&$opts: Options to use for the query
&$join: Join conditions
-'WikiPageDeletionUpdates': manipulate the list of DataUpdates to be applied when
+'WikiPageDeletionUpdates': manipulate the list of DeferrableUpdates to be applied when
a page is deleted. Called in WikiPage::getDeletionUpdates(). Note that updates
specific to a content model should be provided by the respective Content's
getDeletionUpdates() method.
$page: the WikiPage
-$content: the Content to generate updates for (or null, if the Content could not be loaded
-due to an error)
-&$updates: the array of DataUpdate objects. Hook function may want to add to it.
+$content: the Content to generate updates for, or null in case the page revision could not be
+ loaded. The delete will succeed despite this.
+&$updates: the array of objects that implement DeferrableUpdate. Hook function may want to add to
+ it.
'WikiPageFactory': Override WikiPage class used for a title
$title: Title of the page
* @file
*/
-use \MediaWiki\Logger\LoggerFactory;
-use \MediaWiki\MediaWikiServices;
+use MediaWiki\Edit\PreparedEdit;
+use MediaWiki\Logger\LoggerFactory;
+use MediaWiki\MediaWikiServices;
+use Wikimedia\Assert\Assert;
+use Wikimedia\Rdbms\FakeResultWrapper;
+use Wikimedia\Rdbms\IDatabase;
+use Wikimedia\Rdbms\DBError;
+use Wikimedia\Rdbms\DBUnexpectedError;
/**
* Class representing a MediaWiki article and history.
public $mLatest = false; // !< Integer (false means "not loaded")
/**@}}*/
- /** @var stdClass Map of cache fields (text, parser output, ect) for a proposed/new edit */
+ /** @var PreparedEdit Map of cache fields (text, parser output, ect) for a proposed/new edit */
public $mPreparedEdit = false;
/**
*/
protected $mLinksUpdated = '19700101000000';
- const PURGE_CDN_CACHE = 1; // purge CDN cache for page variant URLs
- const PURGE_CLUSTER_PCACHE = 2; // purge parser cache in the local datacenter
- const PURGE_GLOBAL_PCACHE = 4; // set page_touched to clear parser cache in all datacenters
- const PURGE_ALL = 7;
-
/**
* Constructor and clear the article
* @param Title $title Reference to a Title object.
* @return WikiPage|null
*/
public static function newFromID( $id, $from = 'fromdb' ) {
- // page id's are never 0 or negative, see bug 61166
+ // page ids are never 0 or negative, see T63166
if ( $id < 1 ) {
return null;
}
$from = self::convertSelectType( $from );
$db = wfGetDB( $from === self::READ_LATEST ? DB_MASTER : DB_REPLICA );
+ $pageQuery = self::getQueryInfo();
$row = $db->selectRow(
- 'page', self::selectFields(), [ 'page_id' => $id ], __METHOD__ );
+ $pageQuery['tables'], $pageQuery['fields'], [ 'page_id' => $id ], __METHOD__,
+ [], $pageQuery['joins']
+ );
if ( !$row ) {
return null;
}
*/
private static function convertSelectType( $type ) {
switch ( $type ) {
- case 'fromdb':
- return self::READ_NORMAL;
- case 'fromdbmaster':
- return self::READ_LATEST;
- case 'forupdate':
- return self::READ_LOCKING;
- default:
- // It may already be an integer or whatever else
- return $type;
+ case 'fromdb':
+ return self::READ_NORMAL;
+ case 'fromdbmaster':
+ return self::READ_LATEST;
+ case 'forupdate':
+ return self::READ_LOCKING;
+ default:
+ // It may already be an integer or whatever else
+ return $type;
}
}
* @todo Move this UI stuff somewhere else
*
* @see ContentHandler::getActionOverrides
+ * @return array
*/
public function getActionOverrides() {
return $this->getContentHandler()->getActionOverrides();
$this->mTimestamp = '';
$this->mIsRedirect = false;
$this->mLatest = false;
- // Bug 57026: do not clear mPreparedEdit since prepareTextForEdit() already checks
+ // T59026: do not clear mPreparedEdit since prepareTextForEdit() already checks
// the requested rev ID and content against the cached one for equality. For most
// content types, the output should not change during the lifetime of this cache.
// Clearing it can cause extra parses on edit for no reason.
* Return the list of revision fields that should be selected to create
* a new page.
*
+ * @deprecated since 1.31, use self::getQueryInfo() instead.
* @return array
*/
public static function selectFields() {
global $wgContentHandlerUseDB, $wgPageLanguageUseDB;
+ wfDeprecated( __METHOD__, '1.31' );
+
$fields = [
'page_id',
'page_namespace',
return $fields;
}
+ /**
+ * Return the tables, fields, and join conditions to be selected to create
+ * a new page object.
+ * @since 1.31
+ * @return array With three keys:
+ * - tables: (string[]) to include in the `$table` to `IDatabase->select()`
+ * - fields: (string[]) to include in the `$vars` to `IDatabase->select()`
+ * - joins: (array) to include in the `$join_conds` to `IDatabase->select()`
+ */
+ public static function getQueryInfo() {
+ global $wgContentHandlerUseDB, $wgPageLanguageUseDB;
+
+ $ret = [
+ 'tables' => [ 'page' ],
+ 'fields' => [
+ 'page_id',
+ 'page_namespace',
+ 'page_title',
+ 'page_restrictions',
+ 'page_is_redirect',
+ 'page_is_new',
+ 'page_random',
+ 'page_touched',
+ 'page_links_updated',
+ 'page_latest',
+ 'page_len',
+ ],
+ 'joins' => [],
+ ];
+
+ if ( $wgContentHandlerUseDB ) {
+ $ret['fields'][] = 'page_content_model';
+ }
+
+ if ( $wgPageLanguageUseDB ) {
+ $ret['fields'][] = 'page_lang';
+ }
+
+ return $ret;
+ }
+
/**
* Fetch a page record with the given conditions
* @param IDatabase $dbr
* @return object|bool Database result resource, or false on failure
*/
protected function pageData( $dbr, $conditions, $options = [] ) {
- $fields = self::selectFields();
+ $pageQuery = self::getQueryInfo();
// Avoid PHP 7.1 warning of passing $this by reference
$wikiPage = $this;
- Hooks::run( 'ArticlePageDataBefore', [ &$wikiPage, &$fields ] );
+ Hooks::run( 'ArticlePageDataBefore', [
+ &$wikiPage, &$pageQuery['fields'], &$pageQuery['tables'], &$pageQuery['joins']
+ ] );
- $row = $dbr->selectRow( 'page', $fields, $conditions, __METHOD__, $options );
+ $row = $dbr->selectRow(
+ $pageQuery['tables'],
+ $pageQuery['fields'],
+ $conditions,
+ __METHOD__,
+ $options,
+ $pageQuery['joins']
+ );
Hooks::run( 'ArticlePageDataAfter', [ &$wikiPage, &$row ] );
if ( is_int( $from ) ) {
list( $index, $opts ) = DBAccessObjectUtils::getDBOptions( $from );
- $data = $this->pageDataFromTitle( wfGetDB( $index ), $this->mTitle, $opts );
+ $loadBalancer = MediaWikiServices::getInstance()->getDBLoadBalancer();
+ $db = $loadBalancer->getConnection( $index );
+ $data = $this->pageDataFromTitle( $db, $this->mTitle, $opts );
if ( !$data
&& $index == DB_REPLICA
- && wfGetLB()->getServerCount() > 1
- && wfGetLB()->hasOrMadeRecentMasterChanges()
+ && $loadBalancer->getServerCount() > 1
+ && $loadBalancer->hasOrMadeRecentMasterChanges()
) {
$from = self::READ_LATEST;
list( $index, $opts ) = DBAccessObjectUtils::getDBOptions( $from );
- $data = $this->pageDataFromTitle( wfGetDB( $index ), $this->mTitle, $opts );
+ $db = $loadBalancer->getConnection( $index );
+ $data = $this->pageDataFromTitle( $db, $this->mTitle, $opts );
}
} else {
// No idea from where the caller got this data, assume replica DB.
$this->mLinksUpdated = wfTimestampOrNull( TS_MW, $data->page_links_updated );
$this->mIsRedirect = intval( $data->page_is_redirect );
$this->mLatest = intval( $data->page_latest );
- // Bug 37225: $latest may no longer match the cached latest Revision object.
+ // T39225: $latest may no longer match the cached latest Revision object.
// Double-check the ID of any cached latest Revision object for consistency.
if ( $this->mLastRevision && $this->mLastRevision->getId() != $this->mLatest ) {
$this->mLastRevision = null;
$cache = ObjectCache::getMainWANInstance();
return $cache->getWithSetCallback(
- $cache->makeKey( 'page', 'content-model', $this->getLatest() ),
+ $cache->makeKey( 'page-content-model', $this->getLatest() ),
$cache::TTL_MONTH,
function () {
$rev = $this->getRevision();
* @return Revision|null
*/
public function getOldestRevision() {
-
// Try using the replica DB first, then try the master
- $continue = 2;
- $db = wfGetDB( DB_REPLICA );
- $revSelectFields = Revision::selectFields();
-
- $row = null;
- while ( $continue ) {
- $row = $db->selectRow(
- [ 'revision' ],
- $revSelectFields,
- [
- 'rev_page' => $this->getId()
- ],
- __METHOD__,
- [
- 'ORDER BY' => 'rev_timestamp ASC'
- ]
- );
-
- if ( $row ) {
- $continue = 0;
- } else {
- $db = wfGetDB( DB_MASTER );
- $continue--;
- }
+ $rev = $this->mTitle->getFirstRevision();
+ if ( !$rev ) {
+ $rev = $this->mTitle->getFirstRevision( Title::GAID_FOR_UPDATE );
}
-
- return $row ? Revision::newFromRow( $row ) : null;
+ return $rev;
}
/**
}
if ( $this->mDataLoadedFrom == self::READ_LOCKING ) {
- // Bug 37225: if session S1 loads the page row FOR UPDATE, the result always
+ // T39225: if session S1 loads the page row FOR UPDATE, the result always
// includes the latest changes committed. This is true even within REPEATABLE-READ
// transactions, where S1 normally only sees changes committed before the first S1
// SELECT. Thus we need S1 to also gets the revision row FOR UPDATE; otherwise, it
$revision = Revision::newFromPageId( $this->getId(), $latest, $flags );
} else {
$dbr = wfGetDB( DB_REPLICA );
- $revision = Revision::newKnownCurrent( $dbr, $this->getId(), $latest );
+ $revision = Revision::newKnownCurrent( $dbr, $this->getTitle(), $latest );
}
if ( $revision ) { // sanity
* Determine whether a page would be suitable for being counted as an
* article in the site_stats table based on the title & its content
*
- * @param object|bool $editInfo (false): object returned by prepareTextForEdit(),
+ * @param PreparedEdit|bool $editInfo (false): object returned by prepareTextForEdit(),
* if false, the current database state will be used
* @return bool
*/
}
// Update the DB post-send if the page has not cached since now
- $that = $this;
$latest = $this->getLatest();
DeferredUpdates::addCallableUpdate(
- function () use ( $that, $retval, $latest ) {
- $that->insertRedirectEntry( $retval, $latest );
+ function () use ( $retval, $latest ) {
+ $this->insertRedirectEntry( $retval, $latest );
},
DeferredUpdates::POSTSEND,
wfGetDB( DB_MASTER )
$dbr = wfGetDB( DB_REPLICA );
- if ( $dbr->implicitGroupby() ) {
- $realNameField = 'user_real_name';
- } else {
- $realNameField = 'MIN(user_real_name) AS user_real_name';
- }
-
$tables = [ 'revision', 'user' ];
$fields = [
'user_id' => 'rev_user',
'user_name' => 'rev_user_text',
- $realNameField,
+ 'user_real_name' => 'MIN(user_real_name)',
'timestamp' => 'MAX(rev_timestamp)',
];
*
* @since 1.19
* @param ParserOptions $parserOptions ParserOptions to use for the parse operation
- * @param null|int $oldid Revision ID to get the text from, passing null or 0 will
- * get the current revision (default value)
- * @param bool $forceParse Force reindexing, regardless of cache settings
+ * @param null|int $oldid Revision ID to get the text from, passing null or 0 will
+ * get the current revision (default value)
+ * @param bool $forceParse Force reindexing, regardless of cache settings
* @return bool|ParserOutput ParserOutput or false if the revision was not found
*/
public function getParserOutput(
) {
$useParserCache =
( !$forceParse ) && $this->shouldCheckParserCache( $parserOptions, $oldid );
+
+ if ( $useParserCache && !$parserOptions->isSafeToCache() ) {
+ throw new InvalidArgumentException(
+ 'The supplied ParserOptions are not safe to cache. Fix the options or set $forceParse = true.'
+ );
+ }
+
wfDebug( __METHOD__ .
': using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
if ( $parserOptions->getStubThreshold() ) {
}
if ( $useParserCache ) {
- $parserOutput = ParserCache::singleton()->get( $this, $parserOptions );
+ $parserOutput = MediaWikiServices::getInstance()->getParserCache()
+ ->get( $this, $parserOptions );
if ( $parserOutput !== false ) {
return $parserOutput;
}
/**
* Perform the actions of a page purging
- * @param integer $flags Bitfield of WikiPage::PURGE_* constants
* @return bool
+ * @note In 1.28 (and only 1.28), this took a $flags parameter that
+ * controlled how much purging was done.
*/
- public function doPurge( $flags = self::PURGE_ALL ) {
+ public function doPurge() {
// Avoid PHP 7.1 warning of passing $this by reference
$wikiPage = $this;
return false;
}
- if ( ( $flags & self::PURGE_GLOBAL_PCACHE ) == self::PURGE_GLOBAL_PCACHE ) {
- // Set page_touched in the database to invalidate all DC caches
- $this->mTitle->invalidateCache();
- } elseif ( ( $flags & self::PURGE_CLUSTER_PCACHE ) == self::PURGE_CLUSTER_PCACHE ) {
- // Delete the parser options key in the local cluster to invalidate the DC cache
- ParserCache::singleton()->deleteOptionsKey( $this );
- // Avoid sending HTTP 304s in ViewAction to the client who just issued the purge
- $cache = ObjectCache::getLocalClusterInstance();
- $cache->set(
- $cache->makeKey( 'page', 'last-dc-purge', $this->getId() ),
- wfTimestamp( TS_MW ),
- $cache::TTL_HOUR
- );
- }
+ $this->mTitle->invalidateCache();
- if ( ( $flags & self::PURGE_CDN_CACHE ) == self::PURGE_CDN_CACHE ) {
- // Clear any HTML file cache
- HTMLFileCache::clearFileCache( $this->getTitle() );
- // Send purge after any page_touched above update was committed
- DeferredUpdates::addUpdate(
- new CdnCacheUpdate( $this->mTitle->getCdnUrls() ),
- DeferredUpdates::PRESEND
- );
- }
+ // Clear file cache
+ HTMLFileCache::clearFileCache( $this->getTitle() );
+ // Send purge after above page_touched update was committed
+ DeferredUpdates::addUpdate(
+ new CdnCacheUpdate( $this->mTitle->getCdnUrls() ),
+ DeferredUpdates::PRESEND
+ );
if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
$messageCache = MessageCache::singleton();
return true;
}
- /**
- * Get the last time a user explicitly purged the page via action=purge
- *
- * @return string|bool TS_MW timestamp or false
- * @since 1.28
- */
- public function getLastPurgeTimestamp() {
- $cache = ObjectCache::getLocalClusterInstance();
-
- return $cache->get( $cache->makeKey( 'page', 'last-dc-purge', $this->getId() ) );
- }
-
/**
* Insert a new empty page record for this article.
* This *must* be followed up by creating a revision
* page ID is already in use.
*/
public function insertOn( $dbw, $pageId = null ) {
- $pageIdForInsert = $pageId ?: $dbw->nextSequenceValue( 'page_page_id_seq' );
+ $pageIdForInsert = $pageId ? [ 'page_id' => $pageId ] : [];
$dbw->insert(
'page',
[
- 'page_id' => $pageIdForInsert,
'page_namespace' => $this->mTitle->getNamespace(),
'page_title' => $this->mTitle->getDBkey(),
'page_restrictions' => '',
'page_touched' => $dbw->timestamp(),
'page_latest' => 0, // Fill this in shortly...
'page_len' => 0, // Fill this in shortly...
- ],
+ ] + $pageIdForInsert,
__METHOD__,
'IGNORE'
);
if ( $dbw->affectedRows() > 0 ) {
- $newid = $pageId ?: $dbw->insertId();
+ $newid = $pageId ? (int)$pageId : $dbw->insertId();
$this->mId = $newid;
$this->mTitle->resetArticleID( $newid );
$conditions['page_latest'] = $lastRevision;
}
+ $revId = $revision->getId();
+ Assert::parameter( $revId > 0, '$revision->getId()', 'must be > 0' );
+
$row = [ /* SET */
- 'page_latest' => $revision->getId(),
+ 'page_latest' => $revId,
'page_touched' => $dbw->timestamp( $revision->getTimestamp() ),
'page_is_new' => ( $lastRevision === 0 ) ? 1 : 0,
'page_is_redirect' => $rt !== null ? 1 : 0,
* Add row to the redirect table if this is a redirect, remove otherwise.
*
* @param IDatabase $dbw
- * @param Title $redirectTitle Title object pointing to the redirect target,
+ * @param Title|null $redirectTitle Title object pointing to the redirect target,
* or NULL if this is not a redirect
* @param null|bool $lastRevIsRedirect If given, will optimize adding and
* removing rows in redirect table.
* @return bool
*/
public function updateIfNewerOn( $dbw, $revision ) {
-
$row = $dbw->selectRow(
[ 'revision', 'page' ],
[ 'rev_id', 'rev_timestamp', 'page_is_redirect' ],
public function replaceSectionContent(
$sectionId, Content $sectionContent, $sectionTitle = '', $edittime = null
) {
-
$baseRevId = null;
if ( $edittime && $sectionId !== 'new' ) {
- $dbr = wfGetDB( DB_REPLICA );
+ $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
+ $dbr = $lb->getConnection( DB_REPLICA );
$rev = Revision::loadFromTimestamp( $dbr, $this->mTitle, $edittime );
// Try the master if this thread may have just added it.
// This could be abstracted into a Revision method, but we don't want
// to encourage loading of revisions by timestamp.
if ( !$rev
- && wfGetLB()->getServerCount() > 1
- && wfGetLB()->hasOrMadeRecentMasterChanges()
+ && $lb->getServerCount() > 1
+ && $lb->hasOrMadeRecentMasterChanges()
) {
- $dbw = wfGetDB( DB_MASTER );
+ $dbw = $lb->getConnection( DB_MASTER );
$rev = Revision::loadFromTimestamp( $dbw, $this->mTitle, $edittime );
}
if ( $rev ) {
public function replaceSectionAtRev( $sectionId, Content $sectionContent,
$sectionTitle = '', $baseRevId = null
) {
-
if ( strval( $sectionId ) === '' ) {
// Whole-page edit; let the whole text through
$newContent = $sectionContent;
$this->getContentHandler()->getModelID() );
}
- // Bug 30711: always use current version when adding a new section
+ // T32711: always use current version when adding a new section
if ( is_null( $baseRevId ) || $sectionId === 'new' ) {
$oldContent = $this->getContent();
} else {
$old_revision = $this->getRevision(); // current revision
$old_content = $this->getContent( Revision::RAW ); // current revision's content
- if ( $old_content && $old_content->getModel() !== $content->getModel() ) {
- $tags[] = 'mw-contentmodelchange';
+ $handler = $content->getContentHandler();
+ $tag = $handler->getChangeTag( $old_content, $content, $flags );
+ // If there is no applicable tag, null is returned, so we need to check
+ if ( $tag ) {
+ $tags[] = $tag;
+ }
+
+ // Check for undo tag
+ if ( $undidRevId !== 0 && in_array( 'mw-undo', ChangeTags::getSoftwareTags() ) ) {
+ $tags[] = 'mw-undo';
}
- // Provide autosummaries if one is not provided and autosummaries are enabled
+ // Provide autosummaries if summary is not provided and autosummaries are enabled
if ( $wgUseAutomaticEditSummaries && ( $flags & EDIT_AUTOSUMMARY ) && $summary == '' ) {
- $handler = $content->getContentHandler();
$summary = $handler->getAutosummary( $old_content, $content, $flags );
}
$meta = [
'bot' => ( $flags & EDIT_FORCE_BOT ),
'minor' => ( $flags & EDIT_MINOR ) && $user->isAllowed( 'minoredit' ),
- 'serialized' => $editInfo->pst,
+ 'serialized' => $pstContent->serialize( $serialFormat ),
'serialFormat' => $serialFormat,
'baseRevId' => $baseRevId,
'oldRevision' => $old_revision,
/**
* @param Content $content Pre-save transform content
- * @param integer $flags
+ * @param int $flags
* @param User $user
* @param string $summary
* @param array $meta
// Convenience variables
$now = wfTimestampNow();
$oldid = $meta['oldId'];
- /** @var $oldContent Content|null */
+ /** @var Content|null $oldContent */
$oldContent = $meta['oldContent'];
$newsize = $content->getSize();
return $status;
} elseif ( !$oldContent ) {
- // Sanity check for bug 37225
+ // Sanity check for T39225
throw new MWException( "Could not find text for current revision {$oldid}." );
}
- // @TODO: pass content object?!
- $revision = new Revision( [
- 'page' => $this->getId(),
- 'title' => $this->mTitle, // for determining the default content model
- 'comment' => $summary,
- 'minor_edit' => $meta['minor'],
- 'text' => $meta['serialized'],
- 'len' => $newsize,
- 'parent_id' => $oldid,
- 'user' => $user->getId(),
- 'user_text' => $user->getName(),
- 'timestamp' => $now,
- 'content_model' => $content->getModel(),
- 'content_format' => $meta['serialFormat'],
- ] );
-
$changed = !$content->equals( $oldContent );
$dbw = wfGetDB( DB_MASTER );
if ( $changed ) {
+ // @TODO: pass content object?!
+ $revision = new Revision( [
+ 'page' => $this->getId(),
+ 'title' => $this->mTitle, // for determining the default content model
+ 'comment' => $summary,
+ 'minor_edit' => $meta['minor'],
+ 'text' => $meta['serialized'],
+ 'len' => $newsize,
+ 'parent_id' => $oldid,
+ 'user' => $user->getId(),
+ 'user_text' => $user->getName(),
+ 'timestamp' => $now,
+ 'content_model' => $content->getModel(),
+ 'content_format' => $meta['serialFormat'],
+ ] );
+
$prepStatus = $content->prepareSave( $this, $flags, $oldid, $user );
$status->merge( $prepStatus );
if ( !$status->isOK() ) {
throw new MWException( "Failed to update page row to use new revision." );
}
+ $tags = $meta['tags'];
Hooks::run( 'NewRevisionFromEditComplete',
- [ $this, $revision, $meta['baseRevId'], $user ] );
+ [ $this, $revision, $meta['baseRevId'], $user, &$tags ] );
// Update recentchanges
if ( !( $flags & EDIT_SUPPRESS_RC ) ) {
$newsize,
$revisionId,
$patrolled,
- $meta['tags']
+ $tags
);
}
$dbw->endAtomic( __METHOD__ );
$this->mTimestamp = $now;
} else {
- // Bug 32948: revision ID must be set to page {{REVISIONID}} and
+ // T34948: revision ID must be set to page {{REVISIONID}} and
// related variables correctly. Likewise for {{REVISIONUSER}} (T135261).
- $revision->setId( $this->getLatest() );
- $revision->setUserIdAndName(
- $this->getUser( Revision::RAW ),
- $this->getUserText( Revision::RAW )
- );
+ // Since we don't insert a new revision into the database, the least
+ // error-prone way is to reuse given old revision.
+ $revision = $meta['oldRevision'];
}
if ( $changed ) {
/**
* @param Content $content Pre-save transform content
- * @param integer $flags
+ * @param int $flags
* @param User $user
* @param string $summary
* @param array $meta
$wikiPage = $this;
// Trigger post-create hook
$params = [ &$wikiPage, &$user, $content, $summary,
- $flags & EDIT_MINOR, null, null, &$flags, $revision ];
+ $flags & EDIT_MINOR, null, null, &$flags, $revision ];
Hooks::run( 'PageContentInsertComplete', $params );
// Trigger post-save hook
- $params = array_merge( $params, [ &$status, $meta['baseRevId'] ] );
+ $params = array_merge( $params, [ &$status, $meta['baseRevId'], 0 ] );
Hooks::run( 'PageContentSaveComplete', $params );
}
),
/**
* Prepare content which is about to be saved.
- * Returns a stdClass with source, pst and output members
+ *
+ * Prior to 1.30, this returned a stdClass object with the same class
+ * members.
*
* @param Content $content
* @param Revision|int|null $revision Revision object. For backwards compatibility, a
* @param string|null $serialFormat
* @param bool $useCache Check shared prepared edit cache
*
- * @return object
+ * @return PreparedEdit
*
* @since 1.21
*/
// This code path is deprecated, and nothing is known to
// use it, so performance here shouldn't be a worry.
if ( $revid !== null ) {
+ wfDeprecated( __METHOD__ . ' with $revision = revision ID', '1.25' );
$revision = Revision::newFromId( $revid, Revision::READ_LATEST );
} else {
$revision = null;
$user = is_null( $user ) ? $wgUser : $user;
// XXX: check $user->getId() here???
- // Use a sane default for $serialFormat, see bug 57026
+ // Use a sane default for $serialFormat, see T59026
if ( $serialFormat === null ) {
$serialFormat = $content->getContentHandler()->getDefaultFormat();
}
$popts = ParserOptions::newFromUserAndLang( $user, $wgContLang );
Hooks::run( 'ArticlePrepareTextForEdit', [ $this, $popts ] );
- $edit = (object)[];
+ $edit = new PreparedEdit();
if ( $cachedEdit ) {
$edit->timestamp = $cachedEdit->timestamp;
} else {
$edit->newContent = $content;
$edit->oldContent = $this->getContent( Revision::RAW );
- // NOTE: B/C for hooks! don't use these fields!
- $edit->newText = $edit->newContent
- ? ContentHandler::getContentText( $edit->newContent )
- : '';
- $edit->oldText = $edit->oldContent
- ? ContentHandler::getContentText( $edit->oldContent )
- : '';
- $edit->pst = $edit->pstContent ? $edit->pstContent->serialize( $serialFormat ) : '';
-
if ( $edit->output ) {
$edit->output->setCacheTime( wfTimestampNow() );
}
* @param Revision $revision
* @param User $user User object that did the revision
* @param array $options Array of options, following indexes are used:
- * - changed: boolean, whether the revision changed the content (default true)
- * - created: boolean, whether the revision created the page (default false)
- * - moved: boolean, whether the page was moved (default false)
- * - restored: boolean, whether the page was undeleted (default false)
+ * - changed: bool, whether the revision changed the content (default true)
+ * - created: bool, whether the revision created the page (default false)
+ * - moved: bool, whether the page was moved (default false)
+ * - restored: bool, whether the page was undeleted (default false)
* - oldrevision: Revision object for the pre-update revision (default null)
- * - oldcountable: boolean, null, or string 'no-change' (default null):
- * - boolean: whether the page was counted as an article before that
+ * - oldcountable: bool, null, or string 'no-change' (default null):
+ * - bool: whether the page was counted as an article before that
* revision, only used in changed is true and created is false
* - null: if created is false, don't update the article count; if created
* is true, do update the article count
// Save it to the parser cache.
// Make sure the cache time matches page_touched to avoid double parsing.
- ParserCache::singleton()->save(
+ MediaWikiServices::getInstance()->getParserCache()->save(
$editInfo->output, $this, $editInfo->popts,
$revision->getTimestamp(), $editInfo->revid
);
// Update the links tables and other secondary data
if ( $content ) {
- $recursive = $options['changed']; // bug 50785
+ $recursive = $options['changed']; // T52785
$updates = $content->getSecondaryDataUpdates(
$this->getTitle(), null, $recursive, $editInfo->output
);
foreach ( $updates as $update ) {
+ $update->setCause( 'edit-page', $user->getName() );
if ( $update instanceof LinksUpdate ) {
$update->setRevision( $revision );
$update->setTriggeringUser( $user );
$good = 0;
}
$edits = $options['changed'] ? 1 : 0;
- $total = $options['created'] ? 1 : 0;
+ $pages = $options['created'] ? 1 : 0;
- DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, $edits, $good, $total ) );
+ DeferredUpdates::addUpdate( SiteStatsUpdate::factory(
+ [ 'edits' => $edits, 'articles' => $good, 'pages' => $pages ]
+ ) );
DeferredUpdates::addUpdate( new SearchUpdate( $id, $title, $content ) );
// If this is another user's talk page, update newtalk.
if ( $options['created'] ) {
self::onArticleCreate( $this->mTitle );
- } elseif ( $options['changed'] ) { // bug 50785
+ } elseif ( $options['changed'] ) { // T52785
self::onArticleEdit( $this->mTitle, $revision );
}
public function doUpdateRestrictions( array $limit, array $expiry,
&$cascade, $reason, User $user, $tags = null
) {
- global $wgCascadingRestrictionLevels, $wgContLang;
+ global $wgCascadingRestrictionLevels;
if ( wfReadOnly() ) {
- return Status::newFatal( 'readonlytext', wfReadOnlyReason() );
+ return Status::newFatal( wfMessage( 'readonlytext', wfReadOnlyReason() ) );
}
$this->loadPageData( 'fromdbmaster' );
$logAction = 'protect';
}
- // Truncate for whole multibyte characters
- $reason = $wgContLang->truncate( $reason, 255 );
-
$logRelationsValues = [];
$logRelationsField = null;
$logParamsDetails = [];
$dbw->insert(
'page_restrictions',
[
- 'pr_id' => $dbw->nextSequenceValue( 'page_restrictions_pr_id_seq' ),
'pr_page' => $id,
'pr_type' => $action,
'pr_level' => $restrictions,
$cascade = false;
if ( $limit['create'] != '' ) {
+ $commentFields = CommentStore::getStore()->insert( $dbw, 'pt_reason', $reason );
$dbw->replace( 'protected_titles',
[ [ 'pt_namespace', 'pt_title' ] ],
[
'pt_timestamp' => $dbw->timestamp(),
'pt_expiry' => $dbw->encodeExpiry( $expiry['create'] ),
'pt_user' => $user->getId(),
- 'pt_reason' => $reason,
- ], __METHOD__
+ ] + $commentFields, __METHOD__
);
$logParamsDetails[] = [
'type' => 'create',
* @param array|string &$error Array of errors to append to
* @param User $user The deleting user
* @param array $tags Tags to apply to the deletion action
+ * @param string $logsubtype
* @return Status Status object; if successful, $status->value is the log_id of the
* deletion log entry. If the page couldn't be deleted because it wasn't
* found, $status is a non-fatal 'cannotdelete' error
$reason, $suppress = false, $u1 = null, $u2 = null, &$error = '', User $user = null,
$tags = [], $logsubtype = 'delete'
) {
- global $wgUser, $wgContentHandlerUseDB;
+ global $wgUser, $wgContentHandlerUseDB, $wgCommentTableSchemaMigrationStage;
wfDebug( __METHOD__ . "\n" );
$content = null;
}
- $fields = Revision::selectFields();
+ $commentStore = CommentStore::getStore();
+
+ $revQuery = Revision::getQueryInfo();
$bitfield = false;
// Bitfields to further suppress the content
if ( $suppress ) {
$bitfield = Revision::SUPPRESSED_ALL;
- $fields = array_diff( $fields, [ 'rev_deleted' ] );
+ $revQuery['fields'] = array_diff( $revQuery['fields'], [ 'rev_deleted' ] );
}
// For now, shunt the revision data into the archive table.
// Get all of the page revisions
$res = $dbw->select(
- 'revision',
- $fields,
+ $revQuery['tables'],
+ $revQuery['fields'],
[ 'rev_page' => $id ],
__METHOD__,
- 'FOR UPDATE'
+ 'FOR UPDATE',
+ $revQuery['joins']
);
+
// Build their equivalent archive rows
$rowsInsert = [];
+ $revids = [];
+
+ /** @var int[] Revision IDs of edits that were made by IPs */
+ $ipRevIds = [];
+
foreach ( $res as $row ) {
+ $comment = $commentStore->getComment( 'rev_comment', $row );
$rowInsert = [
'ar_namespace' => $namespace,
'ar_title' => $dbKey,
- 'ar_comment' => $row->rev_comment,
'ar_user' => $row->rev_user,
'ar_user_text' => $row->rev_user_text,
'ar_timestamp' => $row->rev_timestamp,
'ar_page_id' => $id,
'ar_deleted' => $suppress ? $bitfield : $row->rev_deleted,
'ar_sha1' => $row->rev_sha1,
- ];
+ ] + $commentStore->insert( $dbw, 'ar_comment', $comment );
if ( $wgContentHandlerUseDB ) {
$rowInsert['ar_content_model'] = $row->rev_content_model;
$rowInsert['ar_content_format'] = $row->rev_content_format;
}
$rowsInsert[] = $rowInsert;
+ $revids[] = $row->rev_id;
+
+ // Keep track of IP edits, so that the corresponding rows can
+ // be deleted in the ip_changes table.
+ if ( (int)$row->rev_user === 0 && IP::isValid( $row->rev_user_text ) ) {
+ $ipRevIds[] = $row->rev_id;
+ }
}
// Copy them into the archive table
$dbw->insert( 'archive', $rowsInsert, __METHOD__ );
// Now that it's safely backed up, delete it
$dbw->delete( 'page', [ 'page_id' => $id ], __METHOD__ );
$dbw->delete( 'revision', [ 'rev_page' => $id ], __METHOD__ );
+ if ( $wgCommentTableSchemaMigrationStage > MIGRATION_OLD ) {
+ $dbw->delete( 'revision_comment_temp', [ 'revcomment_rev' => $revids ], __METHOD__ );
+ }
+
+ // Also delete records from ip_changes as applicable.
+ if ( count( $ipRevIds ) > 0 ) {
+ $dbw->delete( 'ip_changes', [ 'ipc_rev_id' => $ipRevIds ], __METHOD__ );
+ }
// Log the deletion, if the page was suppressed, put it in the suppression log instead
$logtype = $suppress ? 'suppress' : 'delete';
$dbw->onTransactionPreCommitOrIdle(
function () use ( $dbw, $logEntry, $logid ) {
- // Bug 56776: avoid deadlocks (especially from FileDeleteForm)
+ // T58776: avoid deadlocks (especially from FileDeleteForm)
$logEntry->publish( $logid );
},
__METHOD__
$dbw->endAtomic( __METHOD__ );
- $this->doDeleteUpdates( $id, $content, $revision );
+ $this->doDeleteUpdates( $id, $content, $revision, $user );
Hooks::run( 'ArticleDeleteComplete', [
&$wikiPageBeforeDelete,
$status->value = $logid;
// Show log excerpt on 404 pages rather than just a link
- $cache = ObjectCache::getMainStashInstance();
- $key = wfMemcKey( 'page-recent-delete', md5( $logTitle->getPrefixedText() ) );
+ $cache = MediaWikiServices::getInstance()->getMainObjectStash();
+ $key = $cache->makeKey( 'page-recent-delete', md5( $logTitle->getPrefixedText() ) );
$cache->set( $key, 1, $cache::TTL_DAY );
return $status;
/**
* Lock the page row for this title+id and return page_latest (or 0)
*
- * @return integer Returns 0 if no row was found with this title+id
+ * @return int Returns 0 if no row was found with this title+id
* @since 1.27
*/
public function lockAndGetLatest() {
* the required updates. This may be needed because $this->getContent()
* may already return null when the page proper was deleted.
* @param Revision|null $revision The latest page revision
+ * @param User|null $user The user that caused the deletion
*/
- public function doDeleteUpdates( $id, Content $content = null, Revision $revision = null ) {
+ public function doDeleteUpdates(
+ $id, Content $content = null, Revision $revision = null, User $user = null
+ ) {
try {
$countable = $this->isCountable();
} catch ( Exception $ex ) {
}
// Update site status
- DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 1, - (int)$countable, -1 ) );
+ DeferredUpdates::addUpdate( SiteStatsUpdate::factory(
+ [ 'edits' => 1, 'articles' => -$countable, 'pages' => -1 ]
+ ) );
// Delete pagelinks, update secondary indexes, etc
$updates = $this->getDeletionUpdates( $content );
DeferredUpdates::addUpdate( $update );
}
+ $causeAgent = $user ? $user->getName() : 'unknown';
// Reparse any pages transcluding this page
- LinksUpdate::queueRecursiveJobsForTable( $this->mTitle, 'templatelinks' );
-
+ LinksUpdate::queueRecursiveJobsForTable(
+ $this->mTitle, 'templatelinks', 'delete-page', $causeAgent );
// Reparse any pages including this image
if ( $this->mTitle->getNamespace() == NS_FILE ) {
- LinksUpdate::queueRecursiveJobsForTable( $this->mTitle, 'imagelinks' );
+ LinksUpdate::queueRecursiveJobsForTable(
+ $this->mTitle, 'imagelinks', 'delete-page', $causeAgent );
}
// Clear caches
- WikiPage::onArticleDelete( $this->mTitle );
+ self::onArticleDelete( $this->mTitle );
ResourceLoaderWikiModule::invalidateModuleCache(
$this->mTitle, $revision, null, wfWikiID()
);
* @param string $token Rollback token.
* @param bool $bot If true, mark all reverted edits as bot.
*
- * @param array $resultDetails Array contains result-specific array of additional values
+ * @param array &$resultDetails Array contains result-specific array of additional values
* 'alreadyrolled' : 'current' (rev)
* success : 'summary' (str), 'current' (rev), 'target' (rev)
*
* @param string $summary Custom summary. Set to default summary if empty.
* @param bool $bot If true, mark all reverted edits as bot.
*
- * @param array $resultDetails Contains result-specific array of additional values
+ * @param array &$resultDetails Contains result-specific array of additional values
* @param User $guser The user performing the rollback
* @param array|null $tags Change tags to apply to the rollback
* Callers are responsible for permission checks
// Trim spaces on user supplied text
$summary = trim( $summary );
- // Truncate for whole multibyte characters.
- $summary = $wgContLang->truncate( $summary, 255 );
-
// Save
$flags = EDIT_UPDATE | EDIT_INTERNAL;
$targetContent = $target->getContent();
$changingContentModel = $targetContent->getModel() !== $current->getContentModel();
+ if ( in_array( 'mw-rollback', ChangeTags::getSoftwareTags() ) ) {
+ $tags[] = 'mw-rollback';
+ }
+
// Actually store the edit
$status = $this->doEditContent(
$targetContent,
);
// Set patrolling and bot flag on the edits, which gets rollbacked.
- // This is done even on edit failure to have patrolling in that case (bug 62157).
+ // This is done even on edit failure to have patrolling in that case (T64157).
$set = [];
if ( $bot && $guser->isAllowed( 'markbotedits' ) ) {
// Mark all reverted edits as bot
'summary' => $summary,
'current' => $current,
'target' => $target,
- 'newid' => $revId
+ 'newid' => $revId,
+ 'tags' => $tags
];
return [];
MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle( $title );
+ // Invalidate caches of articles which include this page
+ DeferredUpdates::addUpdate(
+ new HTMLCacheUpdate( $title, 'templatelinks', 'page-create' )
+ );
+
if ( $title->getNamespace() == NS_CATEGORY ) {
// Load the Category object, which will schedule a job to create
// the category table row if necessary. Checking a replica DB is ok
*/
public static function onArticleDelete( Title $title ) {
// Update existence markers on article/talk tabs...
+ // Clear Backlink cache first so that purge jobs use more up-to-date backlink information
+ BacklinkCache::get( $title )->clear();
$other = $title->getOtherPage();
$other->purgeSquid();
// Images
if ( $title->getNamespace() == NS_FILE ) {
- DeferredUpdates::addUpdate( new HTMLCacheUpdate( $title, 'imagelinks' ) );
+ DeferredUpdates::addUpdate(
+ new HTMLCacheUpdate( $title, 'imagelinks', 'page-delete' )
+ );
}
// User talk pages
*/
public static function onArticleEdit( Title $title, Revision $revision = null ) {
// Invalidate caches of articles which include this page
- DeferredUpdates::addUpdate( new HTMLCacheUpdate( $title, 'templatelinks' ) );
+ DeferredUpdates::addUpdate(
+ new HTMLCacheUpdate( $title, 'templatelinks', 'page-edit' )
+ );
// Invalidate the caches of all pages which redirect here
- DeferredUpdates::addUpdate( new HTMLCacheUpdate( $title, 'redirect' ) );
+ DeferredUpdates::addUpdate(
+ new HTMLCacheUpdate( $title, 'redirect', 'page-edit' )
+ );
MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle( $title );
HTMLFileCache::clearFileCache( $title );
$revid = $revision ? $revision->getId() : null;
- DeferredUpdates::addCallableUpdate( function() use ( $title, $revid ) {
+ DeferredUpdates::addCallableUpdate( function () use ( $title, $revid ) {
InfoAction::invalidateCache( $title, $revid );
} );
}
*
* @param array $added The names of categories that were added
* @param array $deleted The names of categories that were deleted
- * @param integer $id Page ID (this should be the original deleted page ID)
+ * @param int $id Page ID (this should be the original deleted page ID)
*/
public function updateCategoryCounts( array $added, array $deleted, $id = 0 ) {
$id = $id ?: $this->getId();
);
foreach ( $rows as $row ) {
$cat = Category::newFromRow( $row );
- $cat->refreshCounts();
+ // T166757: do the update after this DB commit
+ DeferredUpdates::addCallableUpdate( function () use ( $cat ) {
+ $cat->refreshCounts();
+ } );
}
}
}
return $this->getTitle()->getCanonicalURL();
}
- /*
+ /**
* @param WANObjectCache $cache
* @return string[]
* @since 1.28